/*=============================================================================
Para compilar:
	gcc montecarlo_paralelo.c -o montecarlo_paralelo -lm -lgmp -pthread

Para isso, é necessário ter instalado a biblioteca GMP: http://gmplib.org/
=============================================================================*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <time.h>
#include <gmp.h>
#include <pthread.h>

/* Número de threads */
#define NUMTHR 4 

/* Outras constantes */
#define NUMERO_PONTOS 1000000000
#define PONTOS_POR_THREAD 250000000

/* 10.000.000 de dígitos, em bits */
/* #define bits_precisao ceil(10000000.0 * log2(10)) */
#define bits_precisao 33219281

/* Registra os pontos criados nas threads */
unsigned long int acertosThread[NUMTHR];

/* Registra o id de cada thread */
unsigned long int id[NUMTHR];

/* Calcula o raio, que é a distância do ponto gerado para a origem */
double raio(double x, double y)
{
    return sqrt(x * x + y * y);
}


/* Função para calcular o tempo de execução */
double tempo()
{
    struct timeval tv;
    gettimeofday(&tv,0);
    return tv.tv_sec + tv.tv_usec/1e6;
}


/* Função executada pelas threads, que sorteia números*/
void *calcula (void *param){
    /* Pega o número da thread */
    int thrnum = *((int *)param);

    /* Variáveis temporárias para armazenar as tentativas e */
    int acertos = 0;

    /* Variável para fazer o loop */
    unsigned int loop;

    /* Coordenadas X e Y*/
    double x, y;

    /* Cada uma das threads calcula x/numero_de_threads pontos */
    for(loop = 0; loop < PONTOS_POR_THREAD; loop++){

	/* Sorteia-se coordenadas x e y, variando de 0 a 1*/
	x = drand48();
	y = drand48();
	
	/* Verifica se esses pontos estão dentro de um círculo de raio 1*/
        if(raio(x,y) <= (double) 1.0) 
	{
		/* Se estiver, contabiliza o acerto*/
		acertos++;			
	}
    }

    acertosThread[thrnum] = acertos;

    pthread_exit(0);
}



int main(void)
{
    /* Variável para percorrer as threads*/
    int i;

    /* Variáveis para registrar o tempo */
    double inicio, fim;

    /* Arquivo de saída */
    FILE *output;

    /* Variáveis para contar o número de pontos e o número de vezes que o ponto gerado estava dentro do círculo*/
    unsigned long int tentativas = 0, acertos = 0;

    /* Variáveis para armazenar o valor calculado de pi, na biblioteca GMP */
    mpf_t Pi;
    
    /* Inicializa as variáveis da biblioteca GMP */
    mpf_init2(Pi, bits_precisao);

    /* Criação das Threads */
    pthread_t tid[NUMTHR];

    /* Semeia os números aleatórios com o tempo da execução */
    srand48(time(NULL));

    /* Prepara o id das threads */
    for (i = 0; i< NUMTHR ; i++){
	id[i] = i;
    }

    /* Registra o tempo inicial */
    inicio = tempo();

    /* Cria as threads*/
    for (i = 0; i< NUMTHR ; i++){
	pthread_create (&tid[i], NULL, calcula, &id[i]);
    }	

    /* Espera as threads executarem*/
    for (i = 0; i< NUMTHR ; i++){
	pthread_join (tid[i], NULL);
    }

    /* Computa os acertos */
    for (i = 0; i< NUMTHR ; i++){
	acertos += acertosThread[i];	
    }	

    /* Contabiliza os acetros e o número de iterações*/
    tentativas += NUMERO_PONTOS;

    /* Calcula pi na biblioteca GMP*/
    mpf_set_ui(Pi, acertos);
    mpf_div_ui(Pi, Pi, tentativas);
    mpf_mul_ui(Pi, Pi, 4);

    /* Registra o tempo final */
    fim = tempo();

    /* Abre o arquivo de saída */
    output = fopen("montecarlo_paralelo.txt", "a+");

    /* Atendida a precisão ou o limite de iterações, imprime os resultados */
    printf("Monte Carlo Paralelo executado com sucesso\n");

    fprintf(output, "\n--------------------------------------------------------\n");
    fprintf(output, " Monte Carlo Paralelo executado com sucesso.\n");
    fprintf(output, " Os primeiros 30 digitos do resultado sao : ");
    mpf_out_str(output, 10, 30, Pi);
    fprintf(output, "\n O tempo gasto com a execucao foi de %lf segundos.\n", (fim - inicio));
    fprintf(output, " O numero de iteracoes necessarias foi %ld.\n", tentativas);

    /* Fecha o ponteiro do arquivo */
    fclose(output);

    /* Desaloca as variáveis usadas */
    mpf_clear(Pi);
 
    return 0;
}
